스트림에서 데이터의 처리 방법은 주로 스트림 API가 관리하기에 편안한 데이터 처리 기법을 제공한다. 이 말은 즉, 스트림 API 내부적으로 다한 최적화(코드의 병렬 실행 등)이 이루어 질 수 있다는 것이다. 이러한 것은 순차적인 반복을 단일 스레드로만 구현하는 외부 반복에서 처리할 수 없다.
해당 장에서는 이러한 스트림API가 지원하는 다양한 연산을 배울 것이며 다음과 같다.
- 필터링, 슬라이싱, 매칭
- 검색, 매칭, 리듀싱
- 특정 범위의 숫자와 같은 숫자 스트림 사용
- 다중 소스로부터 스트림 생성
- 무한 스트림
5.1 필터링
스트림의 요소를 선택하는 방법, 즉 프레디케이트로 필터링 방법과 고유 요소만 필터링하는 방법에 대해 알 필요가 있다.
프레디케이트로 필터링
Boolean을 반환하는 프레디케이트를 인수로 받아서 이에 일치하는 모든 요소를 포함하는 스트림을 반환한다.
```
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
```
해당 코드에서 Dish::isVegetarian이 프레디케이트로 filter함수는 이를 인수로 받아서 필터링한다.
고유 요소 필터링
스트림에서 만든 객체의 hashCode, equals로 결정되는 고유 여부에 의해 판단된 고유 요소로 이루어진 스트림을 반환하는 것이다.
```
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.steram()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println)
```
해당 코드에서는 리스트의 모든 짝수를 선택 후 중복을 필터링하여 고유 오소만 남도록 한다.
5.2 스트림 슬라이싱
스트림의 요소를 선택, 스킵하는 방법을 의미한다.
프레디케이트를 이용한 슬라이싱
자바 9에서 스트림 요소를 효과적으로 선택하도록 takeWhile, dropWhile 의 두 가지 메서드를 지원한다.
해당 메서드들의 효과를 설명하기 위한 데이터 소스는 다음과 같다.
List<Dish> specialMenu = Array.asList( new Dish("seasonal fruit", true, 120, Dish.Type.OTHER), new Dish("prawns", fasle, 300, Dish.Type.FISH), new Dish("rice", true, 350, Dish.Type.OTHER), new Dish("chicken", false, 400, Dish.Type.MEAT), new Dish("french fries" true, 530, Dish.Type.OTHER) );
TakeWhile
위 데이터 소스에서 320 칼로리 이하의 요리를 선택하기 위해 filter를 사용한다고 가정하자.
```
List<Dish> filteredMenu = specialMenu.stream()
.filter(dish -> dish.getCaloreis() < 320)
.collect(toList());
```
filter 연산을 이용하면 전체 스트림을 반복하며 각 요소에 프레디케이트를 적용하여 처리하게 된다. 이 경우 리스트가 정렬되어 있다는 사실을 이용해 320칼로리보다 크거나 같은 요리가 나온 경우 반복 작업을 중단할 수 있다.(filter가 하지 못하는 작업)
그렇지만 여기서 아주 많은 요소가 큰 스트림에서는 연산의 속도에 큰 차이를 줄 수 있다.
이런 경우 takeWhile 연산을 이용할 수 있다.
```
List<Dish> slicedMenu1 = specialMenu.strema()
.takeWhile(dish -> dish.getCalories() < 320)
.collect(toList());
```
이를 이용해 무한 스트림을 포함한 모든 스트림에 프레디케이트를 적용해 스트림을 슬라이스 할 수 있다.
DropWhile
위 에시와 반대로 320칼로리보다 큰 요소를 탐색시 dropWhile을 이용할 수 있따.
```
List<Dish> slicedMenu2 = specialMenu.stream()
.dropWhile(dish -. dish.getCaloreis() < 320)
.collect(toList());
```
dropWhile은 takeWhile과 정반대의 작업을 수행하는데 dropWhile의 경우 프레디케이트가 처음으로 거짓이 되는 지점까지 발견된 요소를 버린다. 프레디케이트가 거짓이 되면 그 지점에서 작업을 중단하고 남은 모든 요소를 반환하게 된다.
스트림 축소
주어진 값 이하의 크기를 갖는 새로운 스트림을 반환하는 것으로 limit 메서드를 지원한다.
```
List<Dish> dishes = specialMenu.stream()
.filter(dish -> dish.getCaloreis() > 300)
.limit(3)
.collect(toList());
```
해당 코드는 프레디케이트와 일치하는 처음 세 요소를 선택한 다음 즉시 결과를 반환하는 코드이다.
요소 건너뛰기
처음 n개 요소를 제외한 스트림을 반환하는 skip 메서드가 존재한다.
처음 n개를 가져오는limit 과 달리 n개 이후 스트림을 반환하기에 상호 보완적인 연산을 수행한다.
```
List<Dish> dishes = menu.stream()
.filter(d -> d.getCaloreis() > 300)
.skip(2)
.collect(toList());
```
5.3 매핑
특정 객체에서 특정 데이터를 선택하는 작업으로 스트림 API의 map, flatMap 메서드가 존재한다.
스트림의 각 요소에 함수 적용
함수를 인수로 받는 방식으로 map 메서드를 지원한다.
인수로 제공된 함수의 경우 각 요소에 적용되며 함수에 적용한결과가 새로운 요소가 된다.
```
List<String> dishNames = menu.stream()
.map(Dish::getName)
.map(Dish::length)
.collect(toList());
```
스트림 평면화
리스트에서 고유 문자로 이루어진 리스트를 반환하는 방법으로 flatMap의 메서드가 존재한다.
Distinct
words.stream() .map(word -> word.split("")) .distinct() .collect(toList());위와 같은 방식으로 처리하게 되면
map으로 전달한 람다가 각 단어의 String[](문자열 배열)을 반환하는 문제가 존재한다. 즉, 원하는 결과는Stream<String>인 반면Stream<String[]>이 된다는 것이다.
```
List<String> uniqueCharacters = words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream) // 생성된 스트림 하나의 스트림으로 평면화
.distinct()
.collect(toList());
```
해당 과정에서 flatMap은 각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑한다. 즉, map(Arrays::stream)과 달리 flatMap은 하나의 평면화된 스트림을 반환하는 것이다.
map -> flatMap : Stream<String[]> -> Stream<String>
이렇게 반환되는 결과의 최종 형태는 List<String>이다.
결국 flatMap메서드는 스트림의 각 값을 다른 스트림으로 만든 다음에 모든 스트림을 하의 스트림으로 연결한다.
5.3 검색과 매칭
특정 속성이 데이터 집합에 존재하는 여부를 처리하기 위해 사용된다.
프레디케이트가 적어도 한 요소와 일치하는지 확인
주어진 스트림에서 적어도 한 요소와 일치하는지 확인하기 위해 사용하는 경우로 anyMatch 메서드를 사용한다.
```
if (menu.stream().anyMatch(Dish::isVegetarian)) {
System.out.println("The menu is (somewhat) vegetarian friendly!!);
}
```
프레디케이트가 모든 요소와 일치하는지 검사
스트림의 모든 요소가 주어진 프레디케이트와 일치하는 지 검사하기 위해 사용하는 경우로 allMatch 메서드가 존재한다.
```
boolean isHealthy = menu.stream()
.allMatch(dish -. dish.getCaloreis() < 1000);
```
Nonematch
noneMatch 의 경우 주어진 프레디케이트와 일치하는 요소가 없는지 확인한다.
```
boolean isHealthy = menu.stream()
.noneMatch(d -> d.getCaloreis() >= 1000);
```
위의 세 메서드(anyMatch, allMatch, noneMatch)는 스트림 쇼트서킷 기법을 사용한다.
[!쇼트서킷 평가] > 전체 스트림을 처리하지 않았더라도 결과를 반환할 수 있는 경우가 존재한다.
allMatch,noneMatch,findFirst, 등의 연산은 모든 스트림의 요소를 처리하지 않고도 결과를 반환하는 쇼트서킷의상황에서도 작동한다.
요소 검색
현재 스트림에서 임의의 요소를 반환한다.
findAny 메서드를 주로 사용하며 다른 스트림 연산과 연결해서 사용가능하다.
```
Optinal<Dish> dish = menu.stream()
.filter(Dish::isVegetarian)
.findAny();
```
해당 경우 내부적으로 단일 과정으로 실행할 수 있도록 최적화하는 즉, 쇼트 서킷을 이용해 결과를 찾는 즉시 실행을 종료한다.
첫 번째 요소 찾기
일부 스트림에는 이미 논리적인 순서가 정해져있을 수 잇다. 이 경우 첫 번째 요소를 찾기 위해 findFirst메서드를 사용한다.
```
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisiableByThree = someNumbers.stream()
.map(n -> n * n)
.filter(n -> n % 3 == 0)
.findFirst(); // 9
```
5.5 리듀싱
스트림 요소를 조합해 더 복잡한 질의를 표현하기 위한 것으로 모든 스트림 요소를 처리해서 값으로 도출하는 연산이다. 이를 함수형 프로그래밍 언어 용어로 fold라고도 부른다.
요소의 합
다음 코드처럼 reduce를 이용해 요소의 합을 구할 수 있다.
```
// for-each loop 이용
int sum = 0;
for (int x : numbers) {
sum += x;
}
// reduce 이용
int sum = numbes.stream().reduce(0, (a, b) -> a + b);
```
위와 같이 reduce를 이용해 반복된 패턴을 추상화하여 처리할 수 있다.
reduce 의 인수는 다음과 같다.
- 초깃값 : 0
- 두 요소를 조합해 새로운 값을 만드는 BinaryOperator<
T>
최댓값과 최솟값
reduce를 활용해 최대, 최소 값을 찾을 수 있다.
```
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
```
Reduce
> 기존의 단계적 반복으로 합계를 구하는 것과 다르게 `reduce`를 이용하면 내부 반복이 추상화되면서 내부 구현에서 병렬로 `reduce`를 실행할 수 있게 된다.
> 예를 들어, 반복적인 합계에서는 `sum`변수를 공유해야 하기에 쉽게 병렬화하기 어렵다. 강제적으로 동기화시켜도 스레드간의 소모적인 경쟁으로 인해 그 이득은 상쇄된다.